/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 * (c) Copyright 1996 - 2001 Gary Henderson (gary.henderson@ntlworld.com) and
 *                           Jerremy Koot (jkoot@snes9x.com)
 *
 * Super FX C emulator code
 * (c) Copyright 1997 - 1999 Ivar (ivar@snes9x.com) and
 *                           Gary Henderson.
 * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_.
 *
 * DSP1 emulator code (c) Copyright 1998 Ivar, _Demo_ and Gary Henderson.
 * C4 asm and some C emulation code (c) Copyright 2000 zsKnight and _Demo_.
 * C4 C code (c) Copyright 2001 Gary Henderson (gary.henderson@ntlworld.com).
 *
 * (c) Copyright 2014 - 2016 Daniel De Matteis. (UNDER NO CIRCUMSTANCE 
 * WILL COMMERCIAL RIGHTS EVER BE APPROPRIATED TO ANY PARTY)
 *
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Snes9x homepage: http://www.snes9x.com
 *
 * Permission to use, copy, modify and distribute Snes9x in both binary and
 * source form, for non-commercial purposes, is hereby granted without fee,
 * providing that this license information and copyright notice appear with
 * all copies and any derived work.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event shall the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Snes9x is freeware for PERSONAL USE only. Commercial users should
 * seek permission of the copyright holders first. Commercial use includes
 * charging money for Snes9x or software derived from Snes9x.
 *
 * The copyright holders request that bug fixes and improvements to the code
 * should be forwarded to them so everyone can benefit from the modifications
 * in future versions.
 *
 * Super NES and Super Nintendo Entertainment System are trademarks of
 * Nintendo Co., Limited and its subsidiary companies.
 */
#include <string.h>
#include "snes9x.h"
#include "srtc.h"
#include "memmap.h"

/***   The format of the rtc_data structure is:

Index Description     Range (nibble)
----- --------------  ---------------------------------------

  0   Seconds low     0-9
  1   Seconds high    0-5

  2   Minutes low     0-9
  3   Minutes high    0-5

  4   Hour low        0-9
  5   Hour high       0-2

  6   Day low         0-9
  7   Day high        0-3

  8   Month           1-C (0xC is December, 12th month)

  9   Year ones       0-9
  A   Year tens       0-9
  B   Year High       9-B  (9=19xx, A=20xx, B=21xx)

  C   Day of week     0-6  (0=Sunday, 1=Monday,...,6=Saturday)

***/

SRTC_DATA           rtc;


static int month_keys[12] = { 1, 4, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6 };


/*********************************************************************************************
 *
 * Note, if you are doing a save state for this game:
 *
 * On save:
 *
 * Call S9xUpdateSrtcTime and save the rtc data structure.
 *
 * On load:
 *
 * restore the rtc data structure
 *      rtc.system_timestamp = time (NULL);
 *
 *
 *********************************************************************************************/


void S9xResetSRTC()
{
   rtc.index = -1;
   rtc.mode = MODE_READ;
}

void S9xHardResetSRTC()
{
   memset(&rtc, 0, sizeof(rtc));
   rtc.index = -1;
   rtc.mode = MODE_READ;
   rtc.count_enable = FALSE;
   rtc.needs_init = TRUE;

   // Get system timestamp
   rtc.system_timestamp = time(NULL);
}

/**********************************************************************************************/
/* S9xSRTCComputeDayOfWeek()                                                                  */
/* Return 0-6 for Sunday-Saturday                                                             */
/**********************************************************************************************/
unsigned int    S9xSRTCComputeDayOfWeek()
{
   unsigned    year = rtc.data[10] * 10 + rtc.data[9];
   unsigned    month = rtc.data[8];
   unsigned    day = rtc.data[7] * 10 + rtc.data[6];
   unsigned    day_of_week;

   year += (rtc.data[11] - 9) * 100;

   // Range check the month for valid array indicies
   if (month > 12)
      month = 1;

   day_of_week = year + (year / 4) + month_keys[month - 1] + day - 1;

   if ((year % 4 == 0) && (month <= 2))
      day_of_week--;

   day_of_week %= 7;

   return day_of_week;
}


/**********************************************************************************************/
/* S9xSRTCDaysInMonth()                                                                       */
/* Return the number of days in a specific month for a certain year                           */
/**********************************************************************************************/
int   S9xSRTCDaysInMmonth(int month, int year)
{
   int     mdays;

   switch (month)
   {
   case 2:
      if ((year % 4 == 0))        // DKJM2 only uses 199x - 22xx
         mdays = 29;
      else
         mdays = 28;
      break;

   case 4:
   case 6:
   case 9:
   case 11:
      mdays = 30;
      break;

   default: // months 1,3,5,7,8,10,12
      mdays = 31;
      break;
   }

   return mdays;
}


#define DAYTICKS (60*60*24)
#define HOURTICKS (60*60)
#define MINUTETICKS 60


/**********************************************************************************************/
/* S9xUpdateSrtcTime()                                                                        */
/* Advance the  S-RTC time if counting is enabled                                             */
/**********************************************************************************************/
void  S9xUpdateSrtcTime()
{
   time_t   cur_systime;
   long    time_diff;

   // Keep track of game time by computing the number of seconds that pass on the system
   // clock and adding the same number of seconds to the S-RTC clock structure.
   // I originally tried using mktime and localtime library functions to keep track
   // of time but some of the GNU time functions fail when the year goes to 2099
   // (and maybe less) and this would have caused a bug with DKJM2 so I'm doing
   // it this way to get around that problem.

   // Note: Dai Kaijyu Monogatari II only allows dates in the range 1996-21xx.

   if (rtc.count_enable && !rtc.needs_init)
   {
      cur_systime = time(NULL);

      // This method assumes one time_t clock tick is one second
      //        which should work on PCs and GNU systems.
      //        If your tick interval is different adjust the
      //        DAYTICK, HOURTICK, and MINUTETICK defines

      time_diff = (long)(cur_systime - rtc.system_timestamp);
      rtc.system_timestamp = cur_systime;

      if (time_diff > 0)
      {
         int      seconds;
         int      minutes;
         int      hours;
         int      days;
         int      month;
         int      year;
         int      temp_days;

         int      year_hundreds;
         int      year_tens;
         int      year_ones;


         if (time_diff > DAYTICKS)
         {
            days = time_diff / DAYTICKS;
            time_diff = time_diff - days * DAYTICKS;
         }
         else
            days = 0;

         if (time_diff > HOURTICKS)
         {
            hours = time_diff / HOURTICKS;
            time_diff = time_diff - hours * HOURTICKS;
         }
         else
            hours = 0;

         if (time_diff > MINUTETICKS)
         {
            minutes = time_diff / MINUTETICKS;
            time_diff = time_diff - minutes * MINUTETICKS;
         }
         else
            minutes = 0;

         if (time_diff > 0)
            seconds = time_diff;
         else
            seconds = 0;


         seconds += (rtc.data[1] * 10 + rtc.data[0]);
         if (seconds >= 60)
         {
            seconds -= 60;
            minutes += 1;
         }

         minutes += (rtc.data[3] * 10 + rtc.data[2]);
         if (minutes >= 60)
         {
            minutes -= 60;
            hours += 1;
         }

         hours += (rtc.data[5] * 10 + rtc.data[4]);
         if (hours >= 24)
         {
            hours -= 24;
            days += 1;
         }

         if (days > 0)
         {
            year =  rtc.data[10] * 10 + rtc.data[9];
            year += (1000 + rtc.data[11] * 100);

            month = rtc.data[8];
            days += (rtc.data[7] * 10 + rtc.data[6]);
            while (days > (temp_days = S9xSRTCDaysInMmonth(month, year)))
            {
               days -= temp_days;
               month += 1;
               if (month > 12)
               {
                  year += 1;
                  month = 1;
               }
            }

            year_tens = year % 100;
            year_ones = year_tens % 10;
            year_tens /= 10;
            year_hundreds = (year - 1000) / 100;

            rtc.data[6] = days % 10;
            rtc.data[7] = days / 10;
            rtc.data[8] = month;
            rtc.data[9] = year_ones;
            rtc.data[10] = year_tens;
            rtc.data[11] = year_hundreds;
            rtc.data[12] = S9xSRTCComputeDayOfWeek();
         }

         rtc.data[0] = seconds % 10;
         rtc.data[1] = seconds / 10;
         rtc.data[2] = minutes % 10;
         rtc.data[3] = minutes / 10;
         rtc.data[4] = hours % 10;
         rtc.data[5] = hours / 10;

         return;
      }
   }
}


/**********************************************************************************************/
/* S9xSetSRTC()                                                                               */
/* This function sends data to the S-RTC used in Dai Kaijyu Monogatari II                     */
/**********************************************************************************************/
void S9xSetSRTC(uint8 data, uint16 Address)
{

   data &= 0x0F; // Data is only 4-bits, mask out unused bits.

   if (data >= 0xD)
   {
      // It's an RTC command

      switch (data)
      {
      case 0xD:
         rtc.mode = MODE_READ;
         rtc.index = -1;
         break;

      case 0xE:
         rtc.mode = MODE_COMMAND;
         break;

      default:
         // Ignore the write if it's an 0xF ???
         // Probably should switch back to read mode -- but this
         //  sequence never occurs in DKJM2
         break;
      }

      return;
   }

   if (rtc.mode == MODE_LOAD_RTC)
   {
      if ((rtc.index >= 0) || (rtc.index < MAX_RTC_INDEX))
      {
         rtc.data[rtc.index++] = data;

         if (rtc.index == MAX_RTC_INDEX)
         {
            // We have all the data for the RTC load

            rtc.system_timestamp = time(NULL);    // Get local system time

            // Get the day of the week
            rtc.data[rtc.index++] = S9xSRTCComputeDayOfWeek();

            // Start RTC counting again
            rtc.count_enable = TRUE;
            rtc.needs_init = FALSE;
         }

         return;
      }
      else
      {
         // Attempting to write too much data
         // error(); // ignore??
      }
   }
   else if (rtc.mode == MODE_COMMAND)
   {
      switch (data)
      {
      case COMMAND_CLEAR_RTC:
         // Disable RTC counter
         rtc.count_enable = FALSE;

         memset(rtc.data, 0, MAX_RTC_INDEX + 1);
         rtc.index = -1;
         rtc.mode = MODE_COMMAND_DONE;
         break;

      case COMMAND_LOAD_RTC:
         // Disable RTC counter
         rtc.count_enable = FALSE;

         rtc.index = 0;  // Setup for writing
         rtc.mode = MODE_LOAD_RTC;
         break;

      default:
         rtc.mode = MODE_COMMAND_DONE;
         // unrecognized command - need to implement.
      }

      return;
   }
   else
   {
      if (rtc.mode == MODE_READ)
      {
         // Attempting to write while in read mode. Ignore.
      }

      if (rtc.mode == MODE_COMMAND_DONE)
      {
         // Maybe this isn't an error.  Maybe we should kick off
         // a new E command.  But is this valid?
      }
   }
}

/**********************************************************************************************/
/* S9xGetSRTC()                                                                               */
/* This function retrieves data from the S-RTC                                                */
/**********************************************************************************************/
uint8 S9xGetSRTC(uint16 Address)
{
   if (rtc.mode == MODE_READ)
   {
      if (rtc.index < 0)
      {
         S9xUpdateSrtcTime();    // Only update it if the game reads it
         rtc.index++;
         return (0x0f);          // Send start marker.
      }
      else if (rtc.index > MAX_RTC_INDEX)
      {
         rtc.index = -1;         // Setup for next set of reads
         return (0x0f);          // Data done marker.
      }
      else
      {
         // Feed out the data
         return rtc.data[rtc.index++];
      }
   }
   else
      return 0x0;
}

void S9xSRTCPreSaveState()
{
   if (Settings.SRTC)
   {
      int s;

      S9xUpdateSrtcTime();

      s = Memory.SRAMSize ?
              (1 << (Memory.SRAMSize + 3)) * 128 : 0;
      if (s > 0x20000)
         s = 0x20000;

      SRAM [s + 0] = rtc.needs_init;
      SRAM [s + 1] = rtc.count_enable;
      memmove(&SRAM [s + 2], rtc.data, MAX_RTC_INDEX + 1);
      SRAM [s + 3 + MAX_RTC_INDEX] = rtc.index;
      SRAM [s + 4 + MAX_RTC_INDEX] = rtc.mode;

#ifdef MSB_FIRST
      SRAM [s + 5  + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >>  0);
      SRAM [s + 6  + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >>  8);
      SRAM [s + 7  + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 16);
      SRAM [s + 8  + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 24);
      SRAM [s + 9  + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 32);
      SRAM [s + 10 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 40);
      SRAM [s + 11 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 48);
      SRAM [s + 12 + MAX_RTC_INDEX] = (uint8)(rtc.system_timestamp >> 56);
#else
      memmove(&SRAM [s + 5 + MAX_RTC_INDEX], &rtc.system_timestamp, 8);
#endif
   }
}

void S9xSRTCPostLoadState()
{
   if (Settings.SRTC)
   {
      int s = Memory.SRAMSize ?
              (1 << (Memory.SRAMSize + 3)) * 128 : 0;
      if (s > 0x20000)
         s = 0x20000;

      rtc.needs_init = SRAM [s + 0];
      rtc.count_enable = SRAM [s + 1];
      memmove(rtc.data, &SRAM [s + 2], MAX_RTC_INDEX + 1);
      rtc.index = SRAM [s + 3 + MAX_RTC_INDEX];
      rtc.mode = SRAM [s + 4 + MAX_RTC_INDEX];

#ifdef MSB_FIRST
      rtc.system_timestamp |= (SRAM [s +  5 + MAX_RTC_INDEX] <<  0);
      rtc.system_timestamp |= (SRAM [s +  6 + MAX_RTC_INDEX] <<  8);
      rtc.system_timestamp |= (SRAM [s +  7 + MAX_RTC_INDEX] << 16);
      rtc.system_timestamp |= (SRAM [s +  8 + MAX_RTC_INDEX] << 24);
      rtc.system_timestamp |= (SRAM [s +  9 + MAX_RTC_INDEX] << 32);
      rtc.system_timestamp |= (SRAM [s + 10 + MAX_RTC_INDEX] << 40);
      rtc.system_timestamp |= (SRAM [s + 11 + MAX_RTC_INDEX] << 48);
      rtc.system_timestamp |= (SRAM [s + 12 + MAX_RTC_INDEX] << 56);
#else
      memmove(&rtc.system_timestamp, &SRAM [s + 5 + MAX_RTC_INDEX], 8);
#endif
      S9xUpdateSrtcTime();
   }
}
